with Ada.Text_IO; use Ada.Text_IO;
with Ada.Task_Identification; use Ada.Task_Identification;
with Ada.Numerics.Discrete_Random;
with global ; use global;
procedure Buffer_N is

   type Task_Unit is new Integer range -1..75;
   type operator_int is new Integer range 0..2;
   type how_many_tasks_to_do is new Integer range 1..2;
   type is_machine_to_fix is new Integer range 1..100;
   type bool is new Integer range 0..1;

   type Tasks is record
      argument1   : Task_Unit;
      argument2   : Task_Unit;
      operator    : operator_int;
      result : Integer;
   end record;

   type Machine is record
      id   : Integer;
      task_in   : Tasks;
      is_busy : bool;
      is_working : bool;
   end record;

   package Rand_Int is new Ada.Numerics.Discrete_Random(Task_Unit);
   package Rand is new Ada.Numerics.Discrete_Random(how_many_tasks_to_do);
   package Rand_Operator is new Ada.Numerics.Discrete_Random(operator_int);
   package Rand_Machine is new Ada.Numerics.Discrete_Random(is_machine_to_fix);

   seed : Rand_Int.Generator;
   seed2 : Rand.Generator;
   seed3 : Rand_Operator.Generator;
   seed_machine : Rand_Machine.Generator;

   type Task_Produced is array (0..4) of Tasks;
   workers_count : Integer :=0;


   --
   --TASKS BUFFOR
   --
   task Buffer_Tasks is
      entry put(item: Tasks);
      entry get(item: out Tasks);
   end Buffer_Tasks;

   task body Buffer_Tasks is
      capacity: constant Integer := MAX_TASKS;
      type Index is mod capacity;
      buffer_pool: array (Index) of Tasks;
      in_position: Index := 0;
      out_position: Index := 0;
      number_of_items: Integer range 0..capacity := 0;
   begin
      for i in Index loop
         buffer_pool(i).argument1 := -1;
      end loop;
      loop
         select
            when number_of_items < capacity =>
               accept put(item: in Tasks) do
                  in_position:=0;
                  while buffer_pool(in_position).argument1 /= -1 loop
                     in_position := in_position +1;
                  end loop;

                  if buffer_pool(in_position).argument1 = -1 then
                     buffer_pool(in_position) := item;
                  end if;
                  number_of_items := number_of_items + 1;
               end put;
         or
            when number_of_items > 0 =>
               accept get(item: out Tasks) do
                  in_position:=0;
                  while buffer_pool(in_position).argument1 = -1 loop
                     in_position := in_position +1;
                  end loop;

                  if buffer_pool(in_position).argument1 /= -1 then
                     item := buffer_pool(in_position);
                     buffer_pool(in_position).argument1 := -1;
                     number_of_items := number_of_items - 1;
                  end if;
                  out_position := out_position + 1;

               end get;
         or
            when number_of_items = capacity =>
               delay 2.0;
               Put_line("Task buffor is full!!! " );

         end select;
      end loop;
   end Buffer_Tasks;
   --
   -- Products array
   --
   task Buffer_Products is
      entry put(item: in  Integer);
      entry get(item: out Integer);
   end Buffer_Products;

   task body Buffer_Products is
      capacity: constant Integer := MAX_PRODUCTS;
      type Index is mod capacity;
      buffer_pool: array (Index) of Integer;
      in_position: Index := 0;
      out_position: Index := 0;
      number_of_items: Integer range 0..capacity := 0;
   begin
      for i in Index loop
         buffer_pool(i) :=2000000;
      end loop;
      loop
         select
            when number_of_items < capacity =>
               accept put(item: in Integer) do
                  in_position:=0;
                  while buffer_pool(in_position) /= 2000000 loop
                     in_position := in_position +1;
                  end loop;

                  if buffer_pool(in_position) = 2000000 then
                     buffer_pool(in_position) := item;
                  end if;
                  number_of_items := number_of_items + 1;

               end put;
         or
            when number_of_items > 0 =>
               accept get(item: out Integer) do
                  in_position:=0;
                  while buffer_pool(in_position) = 2000000 loop
                     in_position := in_position +1;
                  end loop;

                  if buffer_pool(in_position) /= 2000000 then
                     item := Integer(buffer_pool(out_position));
                     buffer_pool(in_position) := 2000000;
                     number_of_items := number_of_items - 1;
                  end if;
               end get;
         or
            when number_of_items = capacity =>
               delay 2.0;
               Put_line("Product buffor is full!!! " );
         end select;
      end loop;
   end Buffer_Products;

   --
   -- Machines
   --
   task Buffer_Machine is
      entry use_Machine(get_Task: in  Tasks; put_Task: out Tasks; is_Broken : out Integer);
      entry stop_Machine;
      entry repair(position: out Integer);
   end Buffer_Machine;

   task body Buffer_Machine is
      capacity: constant Integer := MAX_MACHINES;
      type Index is mod capacity;
      buffer_pool: array (Index) of Machine;

      in_position: Index := 0;
      position : Integer :=0;
      out_position: Index := 0;
      current_task : Tasks;
      probability : is_machine_to_fix;
      int_probability : Integer;
      is_sth_broken : Integer :=0;
      broken_machine_id : Index :=0;
   begin
      for i in Index loop
         buffer_pool(i).is_busy :=0;
         buffer_pool(i).is_working :=0;
      end loop;
      loop
         select
            when is_sth_broken < capacity =>
               accept use_Machine(get_Task: in  Tasks; put_Task: out Tasks; is_Broken : out Integer) do
                  --in_position :=0;

                  while buffer_pool(in_position).is_busy = 1 AND buffer_pool(in_position).is_working =1 loop
                     in_position := in_position +1;
                  end loop;
                  if buffer_pool(in_position).is_busy = 0 AND buffer_pool(in_position).is_working =0 then
                     buffer_pool(in_position).is_busy := 1;
                     probability := Rand_Machine.Random(seed_machine);
                     current_task := get_Task;
                     buffer_pool(in_position).id := Integer(in_position);
                     buffer_pool(in_position).task_in := get_Task;
                     Put_line("Using machine number " & Index'Image(in_position) & " " & Integer'Image(buffer_pool(in_position).id));

                     int_probability := Integer(probability);
                     if int_probability > PROBABILITY_OF_CRASH then
                        if(current_task.operator = 0 ) then current_task.result :=  Integer(current_task.argument1) +  Integer(current_task.argument2);
                        else if(current_task.operator = 1 ) then current_task.result :=  Integer(current_task.argument1) -  Integer(get_Task.argument2);
                           else current_task.result :=  Integer(current_task.argument1) *  Integer(current_task.argument2);
                           end if;

                        end if;

                        is_Broken :=0;
                        put_Task := current_Task;

                     else
                        Put_line("Machine nr " & Index'Image(in_position) & " is broken!!!" );
                        is_Broken :=1;
                        is_sth_broken := is_sth_broken+1;
                        broken_machine_id := in_position;
                        buffer_pool(in_position).is_working :=1;
                        put_Task := current_Task;
                     end if;
                     -- in_position := in_position + 1;

                     --number_of_items := number_of_items + 1;
                  else
                     Put_line("No machines available... " );
                     put_Task := current_Task;

                  end if;

               end use_Machine;
         or
            accept stop_Machine do
               buffer_pool(in_position).is_busy := 0;
               Put_Line("Machine has been stopped");
            end stop_Machine;
         or
            when is_sth_broken > 0 =>
               accept repair (position : out Integer)  do
                  in_position := 0;
                  while(buffer_pool(in_position).is_working = 0 ) loop
                     in_position := in_position +1;

                  end loop;
                  if(buffer_pool(in_position).is_working = 1) then
                     Put_Line("Repairman is fixing the machine number " & Index'Image(in_position));
                     buffer_pool(in_position).is_working := 0;
                     is_sth_broken := is_sth_broken-1;
                     Put_Line("Number of broken tasks: " & Integer'Image(is_sth_broken));

                  end if;
                  position := Integer(in_position);
               end repair;
         or
            delay 2.0;
         end select;
      end loop;
   end Buffer_Machine;

   task type Producer is
   end Producer;
   task body Producer is
      new_task: Tasks;
      how_many_tasks : how_many_tasks_to_do;
   begin
      for i in 1..99 loop
         how_many_tasks := Rand.Random(seed2);

            new_task.argument1 := Rand_Int.Random(seed);
            new_task.argument2 := Rand_Int.Random(seed);
            if new_task.argument1 = -1 then new_task.argument1 := new_task.argument1 +1; end if;
            if new_task.argument2 = -1 then new_task.argument2 := new_task.argument2 +1; end if;
            new_task.operator :=  Rand_Operator.Random(seed3);
            Put_line("Boss produced a task: " & Task_Unit'Image(new_task.argument1) & " " & Task_Unit'Image(new_task.argument2) );

         Buffer_Tasks.put(new_task);
         delay 1.0;

      end loop;
   end Producer;

   task type Worker (ID: Integer)  is
   end Worker;
   task body Worker is
      x,y: Tasks;
      is_broken : Integer;
   begin

      for i in 1..99 loop
         Buffer_Tasks.get(x);
         Put_line("Worker nr " & Integer'Image(ID) & " : " & Task_Unit'Image(x.argument1));
         Buffer_Machine.use_Machine(x,y,is_broken);
         if(is_broken =0 ) then
            Put_line( "Worker get a product: " & Integer'Image(y.result) );
            Buffer_Products.put(y.result);
         else
            Buffer_Tasks.put(y);
         end if;

         Buffer_Machine.stop_Machine;

         delay w_delay;

      end loop;
   end Worker;

   task type Client is
   end Client;
   task body Client is
      x: Integer;
   begin
      for i in 1..33 loop
         Buffer_Products.get(x);
         Put_line(Image(Current_Task) & ": " & Integer'Image(x));
                        delay 1.0;

      end loop;
   end Client;

   task type Repairman is
   end Repairman;
   task body Repairman is
      x : Integer;
   begin
      for i in 1..33 loop

         Buffer_Machine.repair(x);
         Put_line("Repairman fixed a machine nr " & Integer'Image(x));
         --Buffer_Machine.stop_Machine;
                        delay 1.0;

      end loop;
   end Repairman;


   boss: Producer;
   w1 : Worker(1);
   w2 : Worker(2);
   w3 : Worker(3);
   mechanic : Repairman;
   client1, client2 : Client;
begin
   null;
end;
